/*
 * This file is part of Ember.
 *
 * Ember is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ember is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ember.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) Grant Tyra 2010
 * granttyra@gmail.com
*/

#include "../libember/ember_types.h"
#include "../libember/ember_call_stack.h"
#include "../libember/ember_class.h"
#include "../libember/ember_list_type.h"
#include "../libember/ember_native.h"
#include "../libember/ember_statement.h"
#include "../libember/ember_evaluation.h"
#include "../libember/ember_reflection.h"
#include "../libember/ember_string.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

void ReadLine(char *fileBuffer, int begin, int end, EmberList *lines) {
	char *line = NULL;
	int i = 0;
	int length = 0;

	for (i = begin; i < end; i++) {
		if (fileBuffer[i] == ';') {
			end = i;
			break;
		}
	}

	length = end - begin;

	if (length > 1) {
		line = EmberStringCreate(length + 1);
		memcpy(line, fileBuffer + begin, length);
		line[length] = '\0';
		EmberStringTrim(&line);
	}
	
    EmberListAdd(lines, line);
}

void ReadLines(char *fileBuffer, int size, EmberList *lines) {
	int begin = 0;
	int i = 0;

	for (i = 0; i < size; i++) {
		if (fileBuffer[i] == '\n') {
			ReadLine(fileBuffer, begin, i, lines);
			begin = i + 1;
		}
	}
}

int FileSize(FILE *file) {
	int size = 0;
	fseek(file, 0, SEEK_END);
	size = ftell(file);
	rewind(file);
	return size;
}

void LinesFromFile(const char *fileName, EmberList *lines) {
    FILE *file = NULL;
	long size = 0;
	char *buffer = NULL;

	file = fopen(fileName, "r");

	if (file != NULL) {
		size = FileSize(file);
		buffer = EmberStringCreate(size);
	}

	fread(buffer, sizeof(char), size, file);
	ReadLines(buffer, size, lines);
	fclose(file);

	EmberStringDestroy(buffer);
}
	
char *LoadClass(EmberList *lines, int begin, int end, int *past) {
	EmberClassDef *classdef = NULL;
	char *error = EmberClassBuild(&classdef, lines, begin, end, past);

	if (error != NULL) {
		return error;
	}

	if (classdef != NULL) {
		EmberClassMapPut(classdef);
	}

	return NULL;
}

EmberStatement *FindMainMethod() {
	int i = 0;
	EmberClassDef *classdef = NULL;
	EmberMethod *method = NULL;
    EmberList *classes = NULL;

    classes = EmberListCreate();
    EmberClassMapValues(classes);

	for (i = 0; i < classes->size; i++) {
		classdef = EmberListGet(classes, i);
		method = EmberClassGetStaticMethod(classdef, "main");

		if (method != NULL) {
			break;
		}
	}

	EmberListDestroy(classes);

	if (method != NULL) {
		return method->function;
	}

	return NULL;
}

EmberObject *EvaluateMain() {
	EmberStatement *main_statement = FindMainMethod();

	if (main_statement == NULL) {
		return EmberObjectCreateException("Unable to find a static 'main' method.");
	}

	return EmberEvaluate(main_statement);
}

char *Load(const char*);

int FileExists(const char *filename) {
#ifdef WIN32
	WIN32_FIND_DATA data;
	return (int)FindFirstFile(filename, &data);
#else
	return access(filename, F_OK);
#endif
}

char *LoadModule(const char *parent, const char *line) {
	char *module_name = EmberStringAfterString(line, "load ");
	char *local_module = NULL;
	char *local_path = NULL;

	EmberStringTrim(&module_name);
	EmberStringAppend(&module_name, ".emb");

	if (FileExists(module_name) >= 0) {
		Load(module_name);
	} else {
		local_path = EmberStringReverseBefore(parent, '/');
		EmberStringAppend(&local_path, "/");
		local_module = EmberStringConcat(local_path, module_name);
		Load(local_module);
		EmberStringDestroy(local_path);
		EmberStringDestroy(local_module);
	}
    
    EmberStringDestroy(module_name);
	return NULL;
}

char *LoadLine(const char *parent, EmberList *lines, int *index) {
	int i = *index;
	char *line = EmberListGet(lines, i);
	char *error = NULL;

	if (line == NULL) {
		return NULL;
	} else if (EmberStringStartsWithString(line, "class")) {
		error = LoadClass(lines, i, lines->size, index);

		if (error != NULL) {
			return error;
		}
	} else if (EmberStringStartsWithString(line, "load")) {
		error = LoadModule(parent, line);

		if (error != NULL) {
			return error;
		}
	}

	return NULL;
}

char *Load(const char *fileName) {
	int i = 0;
    EmberList *lines = NULL;
	char *line = NULL;

    lines = EmberListCreate();
    LinesFromFile(fileName, lines);

	for (i = 0; i < lines->size; i++) {
		line = LoadLine(fileName, lines, &i);

		if (line != NULL) {
			return EmberStringCopy(line);
		}
    }

	for (i = 0; i < lines->size; i++) {
       EmberStringDestroy(EmberListGet(lines, i));
	}

    EmberListDestroy(lines);
	return NULL;
}

void Initialize() {
	EmberCallStackInitialize();
    EmberClassMapInitialize();

	EmberObjectInstall();
	EmberNullInstall();
	EmberIntegerInstall();
	EmberDecimalInstall();
	EmberStringInstall();
	EmberListInstall();
	EmberDictionaryInstall();
	ember_native_install();
	ember_reflection_install();
}

void Cleanup() {
    EmberCallStackDestroy();
    EmberClassMapDestroy();
}

int EmberExecute(const char *fileName) {
	EmberObject *result = NULL;
	char *line = NULL;
    
 	Initialize();
	line = Load(fileName);

	if (line != NULL) {
		printf("Syntax error: '%s'\n", line);
		return 1;
	}

	result = EvaluateMain();
	Cleanup();

	if (result != NULL && result->typeFlags & TYPE_EXCEPTION) {
		printf("Runtime error: '%s'\n", (char*)EmberObjectGetAttribute(result, "value"));
		return 1;
	}
	
    return 0;
}

int main(int argc, char **argv) {
	if (argc < 2) {
		printf("Ember 0.1.0\n");
		printf("Usage: emberi <module> [options]\n");
	} else {
		EmberExecute(argv[1]);
	}
	
#if defined(WIN32) && defined(_DEBUG)
	system("PAUSE");
#endif

	return 0;
}
